#include <stdio.h>
#include <string.h>
#include <pthread.h>

//#include "common_debug.inc.h"
#include "curl/curl.h"
#include "curlMultiTask.h"
//#include "md5chk.h"

#define u_strcpy(dest, len, src) strncpy(dest, src, len-1)

#define HTTPTASK_LEN_URL        1024
#define HTTPTASK_LEN_FILENAME   1024
#define HTTPTASK_LEN_MD5SUM     36
#define HTTPTASK_LEN_MD5        20
#define HTTPTASK_NUM_MAXTASK    10


struct _HttpTaskItem
{
    //
    char url[HTTPTASK_LEN_URL];
    int timeout;

    char filename[HTTPTASK_LEN_FILENAME];
    bool filemode;

    char* buffer;
    long size_buffer;

    //
    char check_md5sum[HTTPTASK_LEN_MD5SUM];
    long check_size;

    //
    double dltotal;
    double dlnow;
    FILE* fp;
    long index_buffer;

    //md5 count about.
    MD5_CTX md5ctx;
    unsigned char md5ori[HTTPTASK_LEN_MD5];
    size_t dlcount;

    //Status about.
    HttpTaskStatus_e item_status_e;
    double percent;
    double base_percent;
};
typedef struct _HttpTaskItem HttpTaskItem;


struct _HttpTaskItemSet
{
    int num;
    HttpTaskItem httptask[HTTPTASK_NUM_MAXTASK];

    HttpTaskStatus_e status_e;
    double percent;
};
typedef struct _HttpTaskItemSet HttpTaskItemSet;

HttpTaskItemSet gs_httptaskset = {0};


int httptask_init(void)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &gs_httptaskset;
    httptaskset->num = 0;
    int i = 0;
    for(i=0; i<HTTPTASK_NUM_MAXTASK; i++)
    {
        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        httptask = httptask;
        //httptaskitem_init(httptask);
    }
    memset(httptaskset, 0, sizeof(HttpTaskItemSet));

    httptaskset->status_e = STATUS_IDLE;

    return ret;
}


int httptask_deinit(void)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &gs_httptaskset;
    httptaskset->num = 0;
    int i = 0;
    for(i=0; i<HTTPTASK_NUM_MAXTASK; i++)
    {
        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        httptask = httptask;
        //httptaskitem_deinit(httptask);
    }
    memset(httptaskset, 0, sizeof(HttpTaskItemSet));

    httptaskset->status_e = STATUS_IDLE;

    return ret;
}


int httptask_addfiletask(const char* url_s, int timeout_s,
        const char* filename_s,
        const char* checkmd5sum_s, long checksize_s)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &gs_httptaskset;
    int index = httptaskset->num;
    if(index >= HTTPTASK_NUM_MAXTASK)
    {
        DBGPRINTF_ERROR("Add File task error.\n");
        ret = HTTPTASKE_ERROR;
        return ret;
    }
    (httptaskset->num) ++ ;
    HttpTaskItem* httptask = &(httptaskset->httptask[index]);
    u_strcpy(httptask->url, HTTPTASK_LEN_URL, url_s);
    httptask->timeout = timeout_s;
    httptask->filemode = false;
    u_strcpy(httptask->filename, HTTPTASK_LEN_FILENAME, filename_s);
    httptask->filemode = true;

    if(checkmd5sum_s != NULL)
    {
        u_strcpy(httptask->check_md5sum, HTTPTASK_LEN_MD5SUM, checkmd5sum_s);
    }
    httptask->check_size = checksize_s;

    httptask->item_status_e = STATUS_IDLE;
    httptask->percent = -1;

    DBGPRINTF_DEBUG("Add File task OK.\n");

    return ret;
}


int httptask_addbuffertask(const char* url_s, int timeout_s,
        char* buffer_s, long size_buffer_s,
        const char* checkmd5sum_s, long checksize_s)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &gs_httptaskset;
    int index = httptaskset->num;
    if(index >= HTTPTASK_NUM_MAXTASK)
    {
        DBGPRINTF_ERROR("Add File task error.\n");
        ret = HTTPTASKE_ERROR;
        return ret;
    }
    (httptaskset->num) ++ ;
    HttpTaskItem* httptask = &(httptaskset->httptask[index]);
    u_strcpy(httptask->url, HTTPTASK_LEN_URL, url_s);
    httptask->timeout = timeout_s;
    httptask->filemode = false;
    httptask->buffer = buffer_s;
    httptask->size_buffer = size_buffer_s;

    if(checkmd5sum_s != NULL)
    {
        u_strcpy(httptask->check_md5sum, HTTPTASK_LEN_MD5SUM, checkmd5sum_s);
    }
    httptask->check_size = checksize_s;

    httptask->item_status_e = STATUS_IDLE;
    httptask->percent = -1;

    DBGPRINTF_DEBUG("Add Buffer task OK.\n");

    return ret;
}


static int httptask_perform(HttpTaskItemSet* httptaskset);


int httptask_run(void)
{
    int ret = HTTPTASKE_OK;

    DBGPRINTF_INFO("HttpTask run.\n");

    HttpTaskItemSet* httptaskset = &(gs_httptaskset);
    ret = httptask_perform(httptaskset);

    DBGPRINTF_INFO("HttpTask run finished.\n");

    return ret;
}


void* func_httptaskperform(void* arg)
{
    HttpTaskItemSet* httptaskset = (HttpTaskItemSet*)arg;
    httptask_perform(httptaskset);

    return NULL;
}


int httptask_asynrun(void)
{
    int ret = HTTPTASKE_OK;

    DBGPRINTF_INFO("HttpTask Asyn run.\n");

    HttpTaskItemSet* httptaskset = &(gs_httptaskset);

    pthread_t pid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    int ret_pthread = pthread_create(&pid, &attr, func_httptaskperform, httptaskset);
    if(ret_pthread != 0)
    {
        DBGPRINTF_ERROR("HttpTask : AsyncRun create pthread error.\n");
        ret = HTTPTASKE_ERROR;
    }

    DBGPRINTF_INFO("HttpTask Asyn run finished.\n");

    return ret;
}


int httptask_getitemstatus(int i, HttpTaskStatus_e* status_e, int* percent)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &(gs_httptaskset);
    HttpTaskItem* httptask = &(httptaskset->httptask[i]);
    *status_e = httptask->item_status_e;
    *percent = (httptask->percent>=0.0)?(int)(httptask->percent * 100.0 + 0.5):-1;

    return ret;
}


int httptask_getstatus(HttpTaskStatus_e* status_e, int* percent)
{
    int ret = HTTPTASKE_OK;

    HttpTaskItemSet* httptaskset = &(gs_httptaskset);
    *status_e = httptaskset->status_e;
    *percent = (httptaskset->percent>=0.0)?(int)(httptaskset->percent * 100.0 + 0.5):-1;

    return ret;
}


static size_t HttpWriteContentToFp(char *buffer, size_t size, size_t nitems, void *outstream);
static size_t HttpWriteContentToBuffer(char *buffer, size_t size, size_t nitems, void *outstream);
static int WriteProgress(void* p, double dltotal, double dlnow, double ultotal,double ulnow);

int httptask_countbasepercent(HttpTaskItemSet* httptaskset);
int httptaskitem_addrecvedsize(HttpTaskItem* httptask, size_t size_recv);
int httptaskitem_updatepercent(HttpTaskItem* httptask, double percent);

int httptask_perform(HttpTaskItemSet* httptaskset)
{
    int ret = HTTPTASKE_OK;

    DBGPRINTF_DEBUG("HttpTask perform.\n");

    httptask_countbasepercent(httptaskset);

    httptaskset->status_e = STATUS_RUNNING;
    int i = 0;
    bool finishedok = true;
    for(i=0; i<httptaskset->num; i++)
    {
        DBGPRINTF_DEBUG("HttpTask perform : %d.\n", i+1);

        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        httptask->item_status_e = STATUS_RUNNING;
        httptask->percent = 0.0;

        httptask->dlcount = 0;

        //Count md5.
        if((httptask->check_md5sum!=NULL) && (strlen(httptask->check_md5sum)>0))
        {
            MD5Init(&(httptask->md5ctx));
        }

        //Create CURL handle.
        CURLcode ret_curl = CURLE_OK;
        CURL* curl = curl_easy_init();
        if(curl == NULL)
        {
            DBGPRINTF_ERROR("curl_easy_init error.\n");
            httptask->item_status_e = STATUS_FINISHED_ERROR;
            finishedok = false;
            break;
        }

        /*Set url.*/
        curl_easy_setopt(curl, CURLOPT_URL, httptask->url);
        /*Timeout set.*/
        if(httptask->timeout > 0)
        {
            curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
            curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, httptask->timeout);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, httptask->timeout);
        }

        /*Low speed.*/
        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 20L);

        /*Set debug on.*/
        #ifdef HTTPPT_DEBUG
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        #endif

        /*Set progress.*/
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, WriteProgress);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, httptask);

        /*Set callback.*/
        //鍖哄垎璁剧疆 鍐機ontent 鍒版枃浠?or 鍒癰uffer.
        if(httptask->filemode)
        {
            FILE* fp = fopen(httptask->filename, "w");
            if(fp == NULL)
            {
                DBGPRINTF_ERROR("Open file to write failed.[%s].\n", httptask->filename);
                httptask->item_status_e = STATUS_FINISHED_ERROR;
                finishedok = false;
                /* always cleanup */
                curl_easy_cleanup(curl);
                break;
            }
            httptask->fp = fp;
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpWriteContentToFp);
        }
        else
        {
            httptask->index_buffer = 0;
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpWriteContentToBuffer);
        }
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, httptask);

        /* Perform the request, res will get the return code */
        ret_curl = curl_easy_perform(curl);

        if((httptask->filemode) && (httptask->fp != NULL))
        {
            fclose(httptask->fp);
            httptask->fp = NULL;
        }

        /* always cleanup */
        curl_easy_cleanup(curl);

        /* Check return value after curl_easy_perform. */
        if(ret_curl != CURLE_OK)
        {
            DBGPRINTF_ERROR("[CURL]%s.\n", curl_easy_strerror(ret_curl));
            httptask->item_status_e = STATUS_FINISHED_ERROR;
            finishedok = false;
            break;
        }

        /*Check md5 checksum.*/
        if((httptask->check_md5sum!=NULL) && (strlen(httptask->check_md5sum)>0))
        {
            MD5Final(httptask->md5ori, &(httptask->md5ctx));
            char count_md5sum[HTTPTASK_LEN_MD5SUM] = {0};
            int i = 0;
            char tmp[4] = {0};
            for(i=0; i<16; i++)
            {
                memset(tmp, 0, 4);
                snprintf(tmp, 4, "%02x", httptask->md5ori[i]);
                memcpy(count_md5sum+i*2, tmp, 2);
            }
            DBGPRINTF_DEBUG("count_md5sum=%s.\n", count_md5sum);
            DBGPRINTF_DEBUG("check_md5sum=%s.\n", httptask->check_md5sum);

            if(strcasecmp(httptask->check_md5sum, count_md5sum) == 0)
            {
                DBGPRINTF_DEBUG("Md5 check OK.\n");
            }
            else
            {
                DBGPRINTF_ERROR("Md5 check ERROR.\n");
                httptask->item_status_e = STATUS_FINISHED_ERROR;
                finishedok = false;
                break;
            }
        }

        /*Check downloaded size.*/
        if(httptask->check_size!=0)
        {
            size_t dlcount          = (size_t)(httptask->dlcount);
            size_t check_size       = (size_t)(httptask->check_size);
            size_t dlnow            = (size_t)(httptask->dlnow);
            size_t dltotal          = (size_t)(httptask->dltotal);

            if( (dltotal != 0) &&
                !( (dlcount==check_size) && (dlnow==dltotal) && (dlnow==dlcount) ) )
            {
                DBGPRINTF_ERROR("Size check ERROR., [checksize:%ld, dlcount:%u, dlnow:%lf, dltotal:%lf].\n",
                        httptask->check_size, httptask->dlcount, httptask->dlnow, httptask->dltotal);
                httptask->item_status_e = STATUS_FINISHED_ERROR;
                finishedok = false;
                break;
            }

            if( (httptask->dltotal == 0) &&
                !( (dlcount==check_size) && (dlnow==dlcount) ) )
            {
                DBGPRINTF_ERROR("Size check ERROR., [checksize:%ld, dlcount:%u, dlnow:%lf, dltotal:%lf].\n",
                        httptask->check_size, httptask->dlcount, httptask->dlnow, httptask->dltotal);
                httptask->item_status_e = STATUS_FINISHED_ERROR;
                finishedok = false;
                break;
            }
        }

        httptask->percent = 1;
    }

    httptaskset->status_e = finishedok?STATUS_FINISHED_OK:STATUS_FINISHED_ERROR;

    return ret;
}


size_t HttpWriteContentToFp(char *buffer, size_t size, size_t nitems, void *outstream)
{
    HttpTaskItem* httptask = (HttpTaskItem*)outstream;

    //DBGPRINTF_DEBUG("Receive data : %u .\n", nitems);
    FILE* fp = httptask->fp;
    size_t size_recv = fwrite(buffer, size, nitems, fp);
    httptask->dlcount += size_recv;
    httptaskitem_addrecvedsize(httptask, size_recv);

    //Count md5.
    if((httptask->check_md5sum!=NULL) && (strlen(httptask->check_md5sum)>0) && (size_recv > 0))
    {
        MD5Update(&(httptask->md5ctx), (unsigned char*)buffer, size_recv);
    }

    return size_recv;
}


size_t HttpWriteContentToBuffer(char *buffer, size_t size, size_t nitems, void *outstream)
{
    HttpTaskItem* httptask = (HttpTaskItem*)outstream;

    //DBGPRINTF_DEBUG("Receive data : %u .\n", nitems);
    long* index = &(httptask->index_buffer);
    size_t size_recv = 0;
    if((long)(size*nitems) <= httptask->size_buffer - *index)
    {
        size_recv = size*nitems;
        memcpy(httptask->buffer + *index, buffer, size*nitems);
        *index += size*nitems;
    }
    else
    {
        DBGPRINTF_ERROR("Buffer not enought to store downloaded content.\n");
        size_recv = 0 ;
    }

    httptask->dlcount += size_recv;
    httptaskitem_addrecvedsize(httptask, size_recv);

    //Count md5.
    if((httptask->check_md5sum!=NULL) && (strlen(httptask->check_md5sum)>0) && (size_recv > 0))
    {
        MD5Update(&(httptask->md5ctx), (unsigned char*)buffer, size_recv);
    }

    return size_recv;
}


int WriteProgress(void* p, double dltotal, double dlnow, double ultotal,double ulnow)
{
    HttpTaskItem* httptask = (HttpTaskItem*)p;

    //DBGPRINTF_DEBUG("WriteProgress : %lf, %lf .\n", dlnow, dltotal);
    httptask->dltotal = dltotal;
    httptask->dlnow = dlnow;

    if((httptask->dlnow > 0) && (httptask->dlnow != -1))
    {
        //double pre_percent = httptask->percent;
        httptask->percent = httptask->dlnow / httptask->dltotal;
        //double add_percent = httptask->percent - pre_percent;
        httptaskitem_updatepercent(httptask, httptask->percent);
    }

    return 0;
}


int httptaskitem_addrecvedsize(HttpTaskItem* httptask, size_t size_recv)
{
    int ret = HTTPTASKE_OK;


    return ret;
}


int httptaskitem_updatepercent(HttpTaskItem* httptask, double percent)
{
    int ret = HTTPTASKE_OK;
    percent = percent;
    HttpTaskItemSet* httptaskset = &gs_httptaskset;
    httptaskset->percent = 0.0;
    int i = 0;
    for(i=0; i<httptaskset->num; i++)
    {
        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        if(httptask->percent>=0.0)
        {
            httptaskset->percent += httptask->percent * httptask->base_percent ;
        }
    }

    return ret;
}


int httptask_countbasepercent(HttpTaskItemSet* httptaskset)
{
    int ret = HTTPTASKE_OK;

    int i = 0;
    bool sizeset = true;
    long totalsize = 0;
    for(i=0; i<httptaskset->num; i++)
    {
        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        if(httptask->check_size <= 0)
        {
            sizeset = false;
            break;
        }
        totalsize += httptask->check_size;
    }

    for(i=0; i<httptaskset->num; i++)
    {
        HttpTaskItem* httptask = &(httptaskset->httptask[i]);
        httptask->percent = 0;
        if(sizeset)
        {
            httptask->base_percent = double(httptask->check_size) / double(totalsize);
        }
        else
        {
            httptask->base_percent = 1 / httptaskset->num;
        }
    }

    return ret;
}
